home *** CD-ROM | disk | FTP | other *** search
- %PAGESIZE 58,124
- ;*******************************************************************************
- ;
- ; ZDAY and ZDATE
- ;
- ; These are routines for converting Gregorian dates to and from Day Numbers.
- ;
- ; The routines are called using the PASCAL calling sequence, and are FAR calls.
- ; All pointers are FAR pointers.
- ;
- ; The routines are written to run on an 8088 so as to be compatible with all
- ; machines based on this architecture, and use the Borland "PASCAL" calling
- ; sequence so as to be callable from programs compiled with both Borland's C
- ; and Pascal compilers.
- ;
- ; The Borland Turbo Assembler (V2.0 or better) is required to assemble this code.
- ;
- ;===============================================================================
- ;
- ; The C calling sequences are defined by these prototypes:
- ;
- ; unsigned long int far pascal ZDay( unsigned int Year, unsigned int Month,
- ; unsigned int Day );
- ;
- ; int far pascal ZDate(unsigned long int DayNumber, unsigned int far *Year,
- ; unsigned int far *Month, unsigned int far *Day );
- ;
- ;-------------------------------------------------------------------------------
- ;
- ; The PASCAL prototypes (you will $L the object code from this assembly into
- ; a TPU) would be:
- ;
- ; function ZDay(Year, Month, Day : word) : longint;
- ;
- ; function ZDate(DayNumber : longint; var Year, Month, Day : word ) : boolean;
- ;
- ;-------------------------------------------------------------------------------
- ;
- ; ZDay returns a 32-bit unsigned integer representing the Day Number calculated
- ; from the supplied Gregorian date. Although the Pascal call defines it as a
- ; longint, you will not have to worry about getting a negative number back.
- ;
- ; If the resulting Day Number is zero, you have supplied a Gregorian date that
- ; is too early or too far in the future for the calculations to be performed
- ; correctly with this routine's methods.
- ;
- ; The Year, Month and Day parameters are all unsigned 16-bit integers, Year
- ; must be the FULL YEAR. 1902 must be supplied as 1902, not 02. It must be
- ; in the range 1 to 25599 or zero will be returned. Month and Day must be in
- ; the range 0-65535. These restrictions should not be too restrictive for most
- ; purposes.
- ;
- ; Note that Day, Month and Year are all UNSIGNED. When you are doing weird
- ; date calculations you must NEVER try to supply negative values for these.
- ; This is also true of the any Day Number you give to ZDate! Negative numbers
- ; look like large positive numbers and will give Wrong Results!
- ;
- ; The same parameters are used with ZDate, except no value is returned since it
- ; is a procedure (void function) rather than a function. Just supply the Day
- ; Number, and ZDate fills in the Year, Month and Day for you. The supplied Day
- ; Number should be greater than 121 and less than 23920640. For Day Numbers
- ; outside this range, ZDate returns a Gregorian date of 0-0-0 and a return
- ; value of 0 (FALSE in both Pascal and C). A good conversion returns 1 (TRUE
- ; in both C and Pascal).
- ;
- ; NOTE: ZDay can convert certain dates into Day Numbers that are outside the
- ; range that ZDate can convert back. These dates, however, involve years
- ; that are either zero or very, very large, and are well outside the range of
- ; dates that we are likely to be interested in.
- ;
- ; A call to ZDay uses 14 bytes of space on the stack; a call to ZDate uses 26.
- ;
- ;===============================================================================
- ;
- ; WHAT'S IT ALL FOR, ANYWAY?
- ;
- ; All dates, valid or not, convert to Day Numbers of some kind. All Day
- ; Numbers convert to valid Gregorian dates. So if you convert a Gregorian
- ; date to a Day Number and back, if the resulting Gregorian date doesn't match
- ; the original, the original was invalid.
- ;
- ; The Day Number is related to the Julian Day Number, but is not the same.
- ; It is valid only for the years since the Gregorian calendar was introduced.
- ;
- ; You can use the Day Numbers of two dates to find the number of days between
- ; them.
- ;
- ; The remainder resulting when the Day Number is divided by 7 is the day of the
- ; week, with 0 (Sunday) thru 6 (Saturday).
- ;
- ; To find the last day of a month, begin with the Gregorian date. Add 1 to
- ; the month (even to December), set the Day to 0, convert to a Day Number, then
- ; back to Gregorian.
- ;
- ; To find the Julian day of the year (different from the Julian Day Number!),
- ; convert the given Gregorian date to a Day Number. Then subtract from this
- ; the Day Number of January 0 (NOT 1!) of the same year.
- ;
- ; Convert a Julian Date to Gregorian by taking the Day Number of of January
- ; ZERO of the year. Add the Julian day of the year to this, then convert
- ; to a Gregorian date.
- ;
- ;*******************************************************************************
- ; (c) Copyright 1991 Crazy Jack
- ; All Rights Reserved
- %NEWPAGE
- IDEAL
- MODEL LARGE
- CODESEG
- ;
- ; The simplest routine converts the Gregorian date to a Day Number:
- ;
- PROC PASCAL ZDAY FAR Year:WORD, Month:WORD, Day:WORD
- PUBLIC ZDAY
- MOV CX, [Year] ;Get Year.
- MOV BX, [Month] ;Get Month.
- CMP BX, 14 ;Month greater than 14 will give
- JA FIXMNTH ;incorrect results.
- MNTHOK:
- CMP BL, 2 ;Is Month January or February?
- JA NOADJ ;Jump if not,
-
- OR CX, CX ;else be sure year isn't zero (we're
- JZ DATEBAD ;in trouble if we decrement zero),
- DEC CX ;then shift calculations to joint
- ADD BX, 12 ;between February and March.
- NOADJ:
- CMP CH, 100 ;Any year too big to be divided by 100
- JAE DATEBAD ;16-by-8-bit will cause divide overflow.
- INC BX ;Need a little adjustment here---.
-
- MOV AX, 43857 ;Calculate number of days due to months:
- MUL BX ;First multiply by 306001, more than 16
- SHL BX, 1 ;bits worth. The upper word is 4. We
- SHL BX, 1 ;use shifts for speed. Earlier test
- ADD DX, BX ;against 2141 ensures no overflow and
- MOV BX, 10000 ;that we can divide by 10000.
- DIV BX ;This gives INT(month * 30.6001).
- MOV BX, AX ;Save the result (days due to months).
-
- MOV AX, 365 ;Now for days due to years:
- MUL CX ;Gives days in normal years.
- ADD AX, BX ;Add in days due to months.
- ADC DX, 0 ;(There will be no carry from this!)
- PUSH AX ;We need the AX for another divide.
-
- SHR CX, 1 ;Find number of extra days due to
- SHR CX, 1 ;leap years (add a year for every 4).
- MOV AX, CX ;Remove days for 400-year
- MOV BL, 25 ;Gregorian rule:
- DIV BL ;First remove leap year day for each
- XOR AH, AH ;century---
- SUB CX, AX ;Result will always be positive.
- SHR AL, 1 ;Then add back a leap year day for
- SHR AL, 1 ;each 400 years.
- ADD AX, CX ;No carry will occur. Why?
-
- POP BX
- ADD AX, BX ;Add leap year days to days due to
- ADC DX, 0 ;month and year.
- ADD AX, [Day] ;Fold in day of the month.
- ADC DX, 0
- SUB AX, 1 ;Finally, adjust so remainder from
- SBB DX, 0 ;divide by 7 gives day of week.
- ;Back to caller with Day Number
- GONE: ;Day Number in DX:AX.
- RET
- ;
- FIXMNTH: ;Sigh. Month is too big to give valid
- MOV AX, BX ;results, so we must reduce it. We put
- XOR DX, DX ;this here since it won't happen often
- MOV BX, 12 ;and we don't want to slow the main line.
- DIV BX ;We convert it to years and months.
- MOV BX, DX ;Remainder becomes new month.
- ADD CX, AX ;Quotient is years. Add to given year.
- JNC MNTHOK ;Back to conversion if still in range.
- ;
- DATEBAD:
- XOR AX, AX ;If something's wrong, we clear the
- MOV DX, AX ;Day Number in DX:AX to zero
- JMP GONE ;and clear out.
- ;
- ENDP ZDAY
- %NEWPAGE
- ;
- ; Converting a Day Number back to a Gregorian date is more complicated:
- ;
- PROC PASCAL ZDATE FAR DayNumber:WORD:2, Year:FAR PTR WORD, Month:FAR PTR WORD, Day:FAR PTR WORD
- PUBLIC ZDATE
- PUSH SI
- PUSH DI
- MOV DX, [DayNumber+2] ;Get Day Number from caller.
- CMP DX, 365 ;Bigger than this and we can't
- JAE RELAY2 ;extract the year!
- MOV AX, [DayNumber] ;Okay, get the rest of the Day Number.
-
- MOV SI, AX ;We save a copy of it.
- MOV DI, DX
- SUB AX, 121 ;First we back out the 400-year
- SBB DX, 0 ;Gregorian rule.
- JC RELAY1 ;Day number must be greater than 120.
- MOV BX, 48699 ;There are 146097 days in 400 years.
- DIV BX ;146097 = 48699 * 3. We divide in
- MOV CX, DX ;two steps, first by 48699, then by 3.
- XOR DX, DX
- MOV BX, 3
- DIV BX ;The resulting quotient 1/3 of the leap
- ADD SI, AX ;year days removed by the 4000-year rule.
- ADC DI, 0 ;We add them back 3 times, which is
- ADD SI, AX ;quicker than multiplying by 3.
- ADC DI, 0
- INC AX ;Here's the quickest place to add back
- ADD SI, AX ;the 1 we subtracted in ZDay to aid in
- ADC DI, 0 ;finding the day of the week.
- MOV AX, 48699 ;We now finish calculating the remainder
- MUL DX ;from the two divisions which gives us
- ADD AX, CX ;the number of days into the current 400
- ADC DX, 0 ;years.
- MOV CX, DX ;Is there a remainder?
- OR CX, AX
- JZ GREGOUT ;If not, we save some calculation time.
-
- SUB AX, 1 ;Now we calculate the number of leap
- SBB DX, 0 ;years dropped so far THIS 400 years---
- MOV CX, 36524 ;(Number of days in 3 out of 4
- DIV CX ;centuries.)
- ADD SI, AX ;---and add THAT back in.
- ADC DI, 0
- GREGOUT:
- MOV AX, DI ;We now the divide adjusted Day Number
- MOV BX, 100 ;by 365.25 to get the year. The
- MUL BX ;remainder will be the day of the year.
- MOV CX, AX
- MOV AX, SI
- MUL BX
- ADD DX, CX
- SUB AX, 12210
- SBB DX, 0
- RELAY1: JC DAYBAD ;Don't sweat the speed loss on errors.
- MOV CX, 36525
- CMP DX, CX ;Be sure we can divide adjusted value.
- RELAY2: JAE DAYBAD
- DIV CX
- MOV DI, AX ;Year (or year-1) now in the DI.
-
- MOV AX, 1461 ;Note that this gives us a number of
- MUL DI ;days beyond the calculated year, which
- RCR DX, 1 ;is NOT the remainder from the previous
- RCR AX, 1 ;divide.
- RCR DX, 1 ;1461/4 = 365.25, our multiplier.
- RCR AX, 1 ;Since the difference can't exceed
- SUB SI, AX ;487 we only subtract low-order words.
-
- MOV AX, 10000 ;We must now divide the days beyond the
- MUL SI ;calculated year by 30.6001 to get the
- MOV BX, 9871 ;unadjusted month. To do this we multi-
- DIV BX ;ply by 10000, then divide by 306001,
- MOV CX, 31 ;which is more than 16 bits long. Since
- DIV CL ;306001 = 31*9871, we divide in 2 steps.
- MOV CL, AL ;Got the unadjusted month now in the BX.
-
- XCHG DX, BX ;We must now finish getting the
- MOV AL, AH ;remainder which is 10000 times the
- MOV AH, CH ;day of the month.
- MUL DX
- ADD AX, BX
- ADC DX, 0
- MOV BX, 10000 ;Divide out the 10000 and the AX contains
- DIV BX ;one less than the day of the month>
- INC AX
-
- DEC CL ;Adjust start of year back to between
- CMP CL, 12 ;December and January. By now the month
- JBE MNTHRDY ;is less than 16 and the day is in the
- SUB CL, 12 ;range 1-31.
- INC DI
- MNTHRDY:
- MOV DX, DS ;Begin storing results for caller.
- LDS BX, [Day]
- MOV [BX], AX
- MOV AL, 1 ;Set Pascal/C TRUE for caller (AH=0).
- OUTAHERE:
- LDS BX, [Month]
- MOV [BX], CX
- LDS BX, [Year]
- MOV [BX], DI
- ;
- MOV DS, DX ;Restore caller and return.
- POP DI
- POP SI
- RET
- ;
- DAYBAD:
- MOV DX, DS
- XOR AX, AX ;Supplies zero to store and to return.
- LDS BX, [Day] ;Return zeroes for all output values.
- MOV [BX], AX
- MOV CX, AX
- MOV DI, AX
- JMP OUTAHERE ;Go return to caller.
- ;
- ENDP ZDATE
- ;
- ENDS
- %NEWPAGE
- ;*******************************************************************************
- ;
- ; About the Claculations
- ;
- ; There are a number of descriptions of Day Number routines floating around.
- ; The ones I use came from a routine I saw for the HP-65 programmable pocket
- ; calculator to find the Julian Day Number. To it I added adjustments for the
- ; 400-day Gregorian rule. We don't need it for the coming turn of the century
- ; since 200 divides by 400 without a remainder and is, thus, a leap year, but
- ; programmers are picky by nature, so -- what the heck.
- ;
- ; To calculate the Day Number from a Gregorian date we first adjust the year and
- ; month so the month values run from 4 to 15, with March being 4 and February
- ; being 15:
- ;
- ; if Month < 3
- ; then
- ; Add 13 to Month
- ; Subtract 1 from Year
- ; otherwise
- ; Add 1 to Month.
- ;
- ; Now comes the meat of the calculation:
- ;
- ; Day Number = Day of the month
- ; + The Integer Part of (Month * 30.6001)
- ; + The Integer Part of (Year * 365.25)
- ; - The Integer Part of (3/4 of
- ; The Integer Part of (Year / 400)
- ; ).
- ;
- ; That last mess accounts for the Gregorian 400-year rule. Now if we divide
- ; this by 7 (to find the day of the week) we get 0 for Saturday. For a
- ; variety of reasons, mostly related to common practice, I decided to adjust
- ; this further by subtracting 1 from it so Sunday becomes 0.
- ;
- ; Converting back is not so easy.
- ;
- ; First we back out the Gregorian 400-year rule:
- ;
- ; Divide Day Number less 121 by 146097:
- ; Q = the quotient
- ; R = the remainder.
- ;
- ; if R is not 0
- ; then
- ; Add the quotient from ( (R -1) / 36524 ) to Q.
- ;
- ; N = Day Number plus 1 plus Q.
- ;
- ; Note that 145097 is the number of days in 400 years with the 400-year rule
- ; applied, and 36524 is the number of days in a century in which the century
- ; year is NOT a leap year. 36525 is the number of days in a century where the
- ; century year IS a leap year. Once the 400-year rule is backed out, we have
- ; ALL centuries containing 36525 days, which simplifies our calculations.
- ;
- ; Now we can extract the Gregorian date from the adjusted Day Number "N".
- ; First we get the tentative year:
- ;
- ; Year = The Integer Part of ( (N - 122.1) / 365.25 ).
- ;
- ; --and we remove the year's worth of days from the adjusted Day Number "N":
- ;
- ; N = N - The Integer Part of (Year * 365.25).
- ;
- ; From this we extract the tentative month:
- ;
- ; Month = The Integer Part of (N / 30.6001).
- ;
- ; The current day is what is left over when we remove the days due to the
- ; months:
- ;
- ; Day = N - The Integer Ppart of (Month * 30.6001).
- ;
- ; Finally we adjust the Year and the Month:
- ;
- ; if Month > 13
- ; then
- ; Subtract 13 from Month
- ; Add 1 to the Year
- ; else
- ; Subtract 1 from the Month.
- ;
- ; --and the deed is done.
- ;
- ; Since it is my intention that this set of routines be usable on all PC com-
- ; patible systems, we can't assume the availability of a numeric coprocessor or
- ; 32-bit arithmetic, so everything is done in 16-bit integer arithmetic using
- ; the CPU registers and 8086 instructions. In some places it makes the code a
- ; little awkward, but it gives its best performance on the lowliest of PCs
- ; where it's needed the most.
- ;
- ; I chose the Pascal calling sequence so that I could use the code with both C
- ; and Pascal programs. If you don't mind shoving stuff on the stack and the
- ; other fooling around, you can use the routines with assembly code as well.
- ;
- END